﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using Dycox.ComponentModel;

namespace Dycox.Workflows
{
    public static class WorkflowManager
    {
        private static List<Workflow> _workflows;

        public static void RegisterWorkflow(Workflow workflow)
        {
            if (workflow == null)
                throw new ArgumentNullException("workflow");

            if (_workflows == null)
                _workflows = new List<Workflow>();


            if (_workflows.Any(o => o.Name == workflow.Name))
                throw new ArgumentException("Workflow " + workflow.Name + " has already registered.");

            _workflows.Add(workflow);
        }

        /// <summary>
        /// Returns the <see cref="Workflow"/> object which has the given name.
        /// </summary>
        /// <param name="name">Name of workflow.</param>
        /// <returns>If the specified name exists, returns the <see cref="Workflow"/> object. Otherwise, returns null.</returns>
        /// <exception cref="System.ArgumentNullException"><paramref name="name"/> is null.</exception>
        public static Workflow GetWorkflow(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            if (_workflows == null) return null;

            return _workflows.SingleOrDefault(o => o.Name == name);
        }

        internal static Workflow GetWorkflow(string name, bool throwOnError)
        {
            Workflow wf = GetWorkflow(name);

            if (wf == null && throwOnError)
                throw new ArgumentException(SR.GetString("invalid_workflow_name", name), "name");

            return wf;
        }

        public static Workflow GetWorkflow(IWorkflowComponent component)
        {
            if (component == null)
                throw new ArgumentNullException("component");


            if (component == null)
                throw new ArgumentException("Invalid component type.");

            return GetWorkflow(EnsureProcess(component).WorkflowName);
        }

        internal static WFProcess EnsureProcess(IWorkflowComponent component)
        {
            while (!(component is WFProcess) && component != null) component = component.OwnerComponent;
            if (component == null)
                throw new InvalidOperationException("Invalid object model.");

            return (WFProcess)component;
        }

        public static Workflow[] Workflows
        {
            get
            {
                if (_workflows == null)
                    return new Workflow[0];
                else
                    return _workflows.ToArray();
            }
        }

        public static WFStep[] GetActiveSteps()
        {
            return GetActiveSteps(null);
        }

        public static WFStep[] GetActiveSteps(object dataSource)
        {
            ComponentQueryArguments args = new ComponentQueryArguments(dataSource);
            args.IsActive = true;
            args.LoadReferences = Dycox.Linq.LoadReferences.Parents;

            return WFStep.QuerySteps(args, null);
        }

        public static WFStep[] QuerySteps(ComponentQueryArguments args, Linq.PagingInfo paging)
        {
            return WFStep.QuerySteps(args, paging);
        }

        public static WFProcess[] QueryProcesses(ComponentQueryArguments args, Linq.PagingInfo paging)
        {
            return WFProcess.QueryProcess(args, paging);
        }

        public static WFProcess GetProcess(Guid processId)
        {
            ComponentQueryArguments args = new ComponentQueryArguments();
            args.ComponentIDs.Add(new ComponentIdentifier(processId));
            args.LoadReferences = Dycox.Linq.LoadReferences.Children;

            var array = WFProcess.QueryProcess(args, null);

            return array.SingleOrDefault();
        }

        public static WFStep GetStep(ComponentIdentifier id)
        {
            ComponentQueryArguments args = new ComponentQueryArguments();
            args.ComponentIDs.Add(id);
            args.LoadReferences = Linq.LoadReferences.All;

            return WFStep.QuerySteps(args, null).SingleOrDefault();
        }

        public static WFStep GetNearestStep(ComponentIdentifier sampleStepId)
        {
            return GetNearestStep(sampleStepId, null);
        }

        public static WFStep GetNearestStep(ComponentIdentifier sampleStepId, string stageName)
        {
            using (DataProvider dp = DataProvider.CreateProvider())
            {
                WFStep step = dp.GetComponent(sampleStepId, true) as WFStep;
                if (step == null)
                    throw new ArgumentException(SR.GetString("invalid_component_id"));
                return step.FindNearestStep(stageName);
            }
        }

        public static Stage GetStage(string fullName)
        {
            if (fullName == null)
                throw new ArgumentNullException("fullName");

            int p = fullName.IndexOf(Stage.FullNameSeparator);
            if (p > 0)
            {
                string wn, sn;

                wn = fullName.Substring(0, p);
                sn = fullName.Substring(p + 1);

                Workflow wf = WorkflowManager.GetWorkflow(wn);
                if (wf == null)
                    throw new ArgumentException("Workflow " + wn + " not found.");

                Stage st = wf.Stages[sn];
                if (st == null)
                    throw new ArgumentException("Stage " + sn + " of workflow " + wn + " is not defined.");

                return st;
            }
            else
                throw new ArgumentException(fullName + " is not a qualified name.");

        }

        public static Stage[] GetStages(string stageName)
        {
            List<Stage> list = new List<Stage>(5);

            foreach (Workflow wf in Workflows)
            {
                foreach (Stage stage in wf.Stages)
                {
                    if (stage.Name == stageName)
                        list.Add(stage);
                }
            }

            return list.ToArray();
        }

        public static void DeleteProcess(OperationArguments args)
        {
            using (var p = args.CreateProvider())
            {
                p.DeleteProcess(args.ComponentID.ProcessId);
            }
        }

        private static string GetConnectionString()
        {
            var settings = ConfigurationManager.ConnectionStrings["DycoxWorkflows"];

            if (settings == null)
            {
                var app = ConfigurationManager.AppSettings["DycoxWorkflowsConnection"];
                if (app != null)
                    settings = ConfigurationManager.ConnectionStrings[app];
            }

            if (settings != null)
                return settings.ConnectionString;
            else
                return null;
        }

        private static string _connString;

        public static string ConnectionString
        {
            get
            {
                if (_connString == null)
                {
                    _connString = GetConnectionString();
                    if (_connString == null)
                        throw new InvalidOperationException("Connection string not specified.");
                }
                return _connString;
            }
            set { _connString = value; }
        }

        private static ITimeService _timeService;

        public static ITimeService TimeService
        {
            get
            {
                if (_timeService == null)
                    _timeService = new LocalTimeService();
                return _timeService;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                _timeService = value;
            }
        }

        private static IAuthenticationService _authenticationService;

        public static IAuthenticationService AuthenticationService
        {
            get
            {
                if (_authenticationService == null)
                    throw new InvalidOperationException("Authentication service not supplied.");
                return _authenticationService;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                _authenticationService = value;
            }
        }


        internal static FlowControl CreateControl(Workflow workflow)
        {
            return new FlowControl(workflow);
        }

        public static Stage GetStage(this WFStep step)
        {
            if (step == null)
                throw new ArgumentNullException("step");

            Workflow wf = GetWorkflow(step.Thread.Process.WorkflowName);
            if (wf != null) return wf.Stages[step.StageName];
            else return null;
        }

        public static StageContract GetContract(this WFStep step)
        {
            return GetStage(step).GetContract(step);
        }

        public static WFStep[] QueryActiveSteps()
        {
            throw new NotImplementedException();
        }

        private static bool _transactionRequired = true;

        public static bool TransactionRequired
        {
            get { return _transactionRequired; }
            set { _transactionRequired = value; }
        }

    }
}
